"""--------------------------------------------------------------------------------------------------
Filename:   handlers.py
Author:     Chris Chester adapted from Ben Han
Date:       08/02/14
Description:
    Module for the GUI event handlers
--------------------------------------------------------------------------------------------------"""

import wx
import os, sys, traceback
import pickle, copy
from database import Patient, Block, SessionRecord
from config import ConfigPanel, Modes
from Communicate import Console
import datetime


#===============================================================================
# CLASS: MainHandlers
#    Contains event handlers for the main window
#===============================================================================
class MainHandlers:
    def __init__(self, app):
        '''
        ==============  =====================================
        **Arguments:**
        app               (MainApp) Main app class
        ==============  =====================================
       '''
        
        # Save references to main objects
        self.app = app
        
    def new_patient(self, event):
        '''
        Create new patient object. 
        '''
        #Return value is a boolean indicating whether a patient file was written to disk
        cpat = Patient(sessions=[])
        
        # Set the current working directory to the user's home directory by default
        ddir = str(wx.GetHomeDir()) + "\\BISSKIT"
        try:
            os.makedirs(ddir)   # create the default save directory
        except OSError:
            pass    # path already exists
        except:
            sys.stderr.write(traceback.format_exc())
        os.chdir(ddir)
        
        dfile = str(cpat.id)    # default filename set to ID
        file_choices = "Patient files (*.pat)|*.pat"
        dlg = wx.FileDialog(self.app.main_frame,
                            message="Choose a location for the new patient file",
                            defaultDir=os.getcwd(),
                            defaultFile=dfile,
                            wildcard=file_choices,
                            style=wx.FD_SAVE)

        if dlg.ShowModal() == wx.ID_OK:
            cpat.path = dlg.GetPath()       # store path to file in patient object
            
            if os.path.exists(cpat.path):
                # path already exists, ask user to overwrite current files
                msg = "The selected path already exists.\nOverwrite the existing patient file?"
                dlg2 = wx.MessageDialog(None, msg, "Patient file query", wx.ICON_QUESTION | wx.YES_NO | wx.NO_DEFAULT)
                if dlg2.ShowModal() == wx.ID_YES:
                    pass
                else:
                    # if the answer is no then cancel
                    return False
            try:
                file = open(cpat.path, 'wb')
                pickle.dump(cpat, file, pickle.HIGHEST_PROTOCOL)
                file.close()
            except:
                msg = ">> Error saving patient object to file.\n"
                sys.stderr.write(msg)
                wx.MessageDialog(None, msg, "Patient file error", wx.ICON_WARNING | wx.OK).ShowModal()
                sys.stderr.write(traceback.format_exc())
            else:   # file successfully opened
                self.app.status.patient = cpat
                self.app.cfg = cpat.cfg
                self.app.main_frame.update_patient_panel()
                self.app.status.patient_changed = False
                msg = ">> New patient saved to: %s  -- Date: %s\n" % (cpat.path, wx.DateTime.Now().Format())
                self.app.status_msg(msg)
                sys.stdout.write(msg)
                # Create a directory for sessions, same name as patient file
                try:
                    fname = str(dlg.GetFilename())[:-4]
                    ddir = str(dlg.GetDirectory()) + "\\" + fname + "_sessions"
                    os.makedirs(ddir)
                except OSError:
                    pass    # directory already exists
                except:
                    sys.stderr.write(traceback.format_exc())
                return True
        
    def open_patient(self, event):
        '''
        Open previously saved patient object

        '''
        #Set the current working directory to the user's home directory by default
        ddir = str(wx.GetHomeDir()) + "\\BISSKIT"
        if not os.path.isdir(ddir):
            try:
                os.makedirs(ddir)   # create the default save directory
            except OSError:
                pass    # path already exists
            except:
                sys.stderr.write(traceback.format_exc())
        os.chdir(ddir)
        
        file_choices = "Patient files (*.pat)|*.pat"
        dlg = wx.FileDialog(self.app.main_frame,
                            message="Open existing patient file",
                            defaultDir=os.getcwd(),
                            wildcard=file_choices,
                            style=wx.FD_OPEN)

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()    # path to patient file
            try:
                file = open(path, 'rb')
                cpat = self.app.status.patient = pickle.load(file)
                file.close()
            except:
                msg = "Error loading patient object from file.\n"
                sys.stderr.write(msg)
                wx.MessageDialog(None, msg, "Patient file error", wx.ICON_WARNING | wx.OK).ShowModal()
                sys.stderr.write(traceback.format_exc())
            else:
                cpat.path = path        # store path to file in patient object
                self.app.cfg = cpat.cfg
                #self.app.cfg.y_axis_max = self.app.cfg.y_axis_max_init --removed 5/02
                self.app.main_frame.update_patient_panel()
                self.app.main_frame.status_message("Loaded patient file: %s" % path)
                self.app.status.patient_changed = False
                msg = ">> Patient loaded from: %s  -- Date: %s\n" % (path, wx.DateTime.Now().Format())
                self.app.status_msg(msg)
                sys.stdout.write(msg)
            
    def save_patient(self, event):
        '''
        Save currently loaded patient to file
        '''
        
        cpat = self.app.status.patient
        cpat.cfg = self.app.cfg
        if cpat == None:
            self.app.status_msg('No patient loaded')
        else:
            # Set patient object attributes from wx controls
            self.app.main_frame.set_patient_attr()
                
            # Open a save file dialog to set the path
            dfile = copy.copy(cpat.path)
            file_choices = "Patient files (*.pat)|*.pat"
            dlg = wx.FileDialog(self.app.main_frame,
                                message="Save current patient to file",
                                defaultFile=dfile,
                                wildcard=file_choices,
                                style=wx.FD_SAVE)
    
            if dlg.ShowModal() == wx.ID_OK:
                cpat.path = dlg.GetPath()
                try:
                    # open a file object using the provided path and pickle the patient object
                    file = open(cpat.path, 'wb')
                    pickle.dump(cpat, file, pickle.HIGHEST_PROTOCOL)
                    file.close()
                except:
                    msg = "Error saving patient object to file.\n"
                    sys.stderr.write(msg)
                    wx.MessageDialog(None, msg, "Patient file error", wx.ICON_WARNING | wx.OK).ShowModal()
                    sys.stderr.write(traceback.format_exc())
                else:
                    self.app.status.patient_changed = False
                    msg = ">> Patient file saved to: %s  -- Date: %s\n" % (cpat.path, wx.DateTime.Now().Format())
                    self.app.status_msg(msg)
                    sys.stdout.write(msg)
                    # Create a directory for sessions, same name as patient file
                    fname = str(dlg.GetFilename())[:-4]
                    ddir = str(dlg.GetDirectory()) + "\\" + fname + "_sessions"
                    # Create a folder for sessions if it doesn't already exist
                    if not os.path.isdir(ddir):
                        try:
                            os.makedirs(ddir)
                        except OSError:
                            pass    # directory already exists
                        except:
                            sys.stderr.write(traceback.format_exc())
         
    def patient_changed(self, event):
        '''
        Set patient object attributes from wx controls
        '''
        self.app.main_frame.set_patient_attr()
        self.app.status.patient_changed = False
        
    def export_patient(self, event):
        '''
        Export patient file
        '''
        try:
            pat = self.app.status.patient
            path = ""
            if len(pat.session_record) > 0:
                path = pat.export(self.app.main_frame)
                
            if path == "":
                pass    # export cancelled
            else:
                msg = 'Patient:\n    %s-%s\nExported to:\n    %s\n' % (pat.id, pat.name, path)
                sys.stdout.write(msg)
                wx.MessageDialog(None, msg, "Export Patient to CSV files", wx.ICON_INFORMATION | wx.OK).ShowModal()
        except:
            msg = 'Error exporting patient record to CSV files.\n'
            self.app.status_msg(msg)
            sys.stderr.write(msg)
            wx.MessageDialog(None, msg, "Patient file error", wx.ICON_WARNING | wx.OK).ShowModal()
            sys.stderr.write(traceback.format_exc())
            
    def config(self, event):
        '''
        Open the configuration editing window
        '''
        
        self.app.config_panel = ConfigPanel(parent=self.app.main_frame, cfg=self.app.cfg)
        self.app.config_panel.cfg_update()
        self.app.config_panel.MakeModal(True)
        self.app.config_panel.Show()
        
    def new_session(self, event):
        '''
        Start a new session, handler applies to both Strength and Skill training buttons
        '''
        cpat = self.app.status.patient
        if cpat == None:
            self.app.main_frame.status_message('No patient loaded')
        else:
            self.app.main_frame.set_patient_attr()
            self.app.setup_training(event.GetId())
            msg = '>>> New session started at: %s\n' % wx.DateTime.Now().Format()
            sys.stdout.write(msg)
            
            # If already calibrated skip the calibration step

    def session_select(self, event):
        '''
        User has clicked on a row in the session listing
        '''
        cpat = self.app.status.patient
        try:
            row = event.GetRow()    # selected session index
            nsessions = len(cpat.session_record)
            if nsessions == 0:
                pass
            elif row < nsessions:
                # set the currently displayed session to the one selected
                # load session from file
                cpat = self.app.status.patient
                s_rec = cpat.session_record[row]
                i = cpat.path.rfind('\\')
                path = cpat.path[:i+1] + s_rec.path
                try:
                    file = open(path, 'rb')
                    self.app.status.session_disp = pickle.load(file)
                    file.close()
                except:
                    msg = ">> Error loading session from file.\n"
                    sys.stderr.write(msg)
                    wx.MessageDialog(self.app.plot, msg, "Session load error", wx.ICON_WARNING | wx.OK).ShowModal()
                    sys.stderr.write(traceback.format_exc())

                self.app.main_frame.update_session_pane()
        except:
            print traceback.format_exc()

    def export_session(self, event):
        '''
        Export a particular session
        '''
        try:
            sess = self.app.status.session_disp
            path = sess.export(self.app.main_frame)
            if path == "":
                pass        # export cancelled
            else:
                msg = 'Session:\n    %s-%s\nExported to:\n    %s' % (sess.patient, sess.date, path)
                print msg
                wx.MessageDialog(None, msg, "Export session to CSV files", wx.ICON_INFORMATION | wx.OK).ShowModal()
        except:
            msg = ">> Error exporting session.\n"
            sys.stderr.write(msg)
            wx.MessageDialog(None, msg, "Session object error", wx.ICON_WARNING | wx.OK).ShowModal()
            sys.stderr.write(traceback.format_exc())
                
    def exit(self, event):
        '''
        Exit Manin app
        '''
        self.app.on_exit(event)
    
    def about(self, event):
        '''
        Launch about dialogs
        '''
        from dialogs import AboutDialog
        dlg = AboutDialog(self.app.main_frame)
    


#===============================================================================
# CLASS: PlotHandlers
#    Contains event handlers for the control panel
#===============================================================================
class PlotHandlers:
    def __init__(self, app):
        '''
        ==============  =====================================
        **Arguments:**
        app               (MainApp) Main app class
        ==============  =====================================
       '''
        
        # Save a reference to the main application object
        self.app = app
        
    def next_button(self, event):
        '''
        Proceed to the next stage
        '''
        cpat = self.app.status.patient 
        butt_ctrl = self.app.ctrl.but_ctrl
        plot_ctrl = self.app.plot.dCntl
        s_state = self.app.sess_status
        if not s_state.serial_open:
            self.app.status_msg("Serial port is not open")    
        else:
            # Check mode then act accordingly          
            
            if s_state.mode == Modes.StrCal or s_state.mode == Modes.SklCal:          # Calibration modes
                                     
                if cpat.get_calValue() == None: # if a calibration value is not stored, calibrate 
                              
                    if s_state.calib_index == 0:
                        s_state.alive = True            # start the plot
                        self.app.worker.parse = True    # start processing jobs in queue
                        s_state.peak = (0,0)
                        msg = ">> Calibration started"
                        self.app.status_msg(msg)
                        sys.stdout.write(msg+'\n')
                        s_state.calib_array = []        # initialise the calibration array on the first run
                        
                    else:
                        msg = "> Swallow %i marked" % s_state.calib_index
                        self.app.status_msg(msg)
                        sys.stdout.write(msg+'\n')
                        # Add the last maximum point to the calibration array
                        s_state.calib_array.append(s_state.peak[1])
                        
                        s_state.peak = (0,0) # Reset Peak values
                        
                        # finished calibration, parse data, start training
                        if s_state.calib_index >= 5:
                            cpat.set_calValue(s_state.calib_array)
                            print(s_state.calib_array)
                            s_state.alive = False       # pause the plot
                            
                    s_state.calib_index += 1
                    
                if cpat.get_calValue(): # if a calibration value is stored 
                    # End Calibration
                    s_state.mode += 1
                    self.app.parse_calib_data(cpat.get_calValue())
                    
            if s_state.mode == Modes.StrTrl or s_state.mode == Modes.SklTrl:    # Training modes
                if s_state.trial_index == 0:
                    sys.stdout.write(">> Start trials\n")
                    # start the trials
                    s_state.trial_index += 1
                    s_state.alive = True
                    
            self.app.plot.init_plot()
        
        # Change button label and enable/disable buttons
        label = ''
        

        if s_state.mode == Modes.StrCal or s_state.mode == Modes.SklCal:
            if s_state.calib_index < 6:
                label = "Mark %i" % s_state.calib_index
                butt_ctrl.recalib_button.Enable()
                plot_ctrl.recalBtn.setEnabled(True)
        elif s_state.mode == Modes.StrTrl or s_state.mode == Modes.SklTrl:
            label = "Start"
            if not s_state.trial_index == 0:    # if the trial has started
                butt_ctrl.next_button.Disable()
                butt_ctrl.reset_button.Enable()
                butt_ctrl.pause_button.Enable()
                butt_ctrl.recalib_button.Enable()
                plot_ctrl.recalBtn.setEnabled(True)
                plot_ctrl.nextBtn.setEnabled(False)
                plot_ctrl.resetBtn.setEnabled(True)
                plot_ctrl.pauseBtn.setEnabled(True)
        else:
            sys.stderr.write("\nERROR: Couldn't set start button text.\n")
            sys.stderr.write(traceback.format_exc()+'\n')
        butt_ctrl.next_button.SetLabel(label)
        plot_ctrl.nextBtn.setText(label)
        
    def recalibrate(self, event):
        '''
        Restart the calibration process
        '''
        # reset the calibration index
        s_state = self.app.sess_status
        butt_ctrl = self.app.ctrl.but_ctrl
        plot_ctrl = self.app.plot.dCntl
        
        self.app.plot.show_plot(True)
        
        self.app.plot.set_rectangle(size = [0,0]) # Resets target
        self.app.cfg.ymax_auto_value = self.app.cfg.y_axis_max_init
        self.app.plot.set_default_display()
                    
        plot_ctrl.nextBtn.setEnabled(True)
        butt_ctrl.next_button.Enable()
        self.app.sess_status.calib_index = 0
        s_state.calib_array = []
        self.app.status.patient.set_calValue(None)
        
        if s_state.mode == Modes.StrTrl or s_state.mode == Modes.SklTrl: 
            s_state.mode -= 1
        # begin anew
        self.next_button(None)
        
    def pause(self, event):
        '''
        Pause the current plot
        '''
        
        butt_ctrl = self.app.ctrl.but_ctrl
        plot_ctrl = self.app.plot.dCntl
        plot = self.app.plot
        # toggles the sess_status.alive variable to stop and start the plot
        if self.app.sess_status.alive:
            msg = "> Plot paused at: %s\n" % wx.DateTime.Now().Format()
            self.app.status_msg(msg)
            sys.stderr.write(msg)
            self.app.sess_status.alive = False      # stop adding jobs to the queue
            # Plot data acquired while paused is meaningless, so discard any jobs waiting in the queue
            self.app.worker.parse = False
            butt_ctrl.pause_button.SetLabel('Continue')
            plot_ctrl.pauseBtn.setText('Continue')
            plot.pause_time()
            
            if self.app.sess_status.target_on:
                plot.show_rectangle(False)
        else:
            msg = "> Plot unpaused at: %s\n" % wx.DateTime.Now().Format()
            self.app.status_msg(msg)
            sys.stderr.write(msg)
            self.app.sess_status.alive = True
            self.app.worker.parse = True        # start processing jobs in the queue
            butt_ctrl.pause_button.SetLabel('Pause')
            plot_ctrl.pauseBtn.setText('Pause')
            plot.unpause_time()
            
            if self.app.sess_status.target_on:
                plot.show_rectangle(True)
        
        if self.app.sess_status.alive:
            butt_ctrl.save_button.Disable()
            plot_ctrl.saveBtn.setEnabled(False)
            plot_ctrl.saveExitBtn.setEnabled(False)
        else:
            butt_ctrl.save_button.Enable()
            plot_ctrl.saveBtn.setEnabled(True)
            plot_ctrl.saveExitBtn.setEnabled(True)

    def target_reset(self, event):
        '''
        Reset the target position
        '''
        self.app.plot.reset_target()
        sys.stdout.write("> Target reset to initial state\n")
        
    def save_exit(self, event):
        '''
        Save the data and exit the plot
        '''
               
        self.save_sess(event)
            
        self.exit_plot(event)
        
    def save_sess(self, event):
        '''
        Save session to patient and file
        '''
        cpat = self.app.status.patient
        sess = self.app.status.session
        nsess = len(cpat.session_record) + 1
        # Save session object to file at the default location
        # default location is: \path-to-patient-dir\patient-filename_sessions\sess#_session-timestamp
        ppath = cpat.path
        now = datetime.datetime.now()
        
        path = "%s_sessions\\%s-%s-%s_%s.sess" % (ppath[:-4], now.day,now.month,now.year , str(nsess))
        try:
            file = open(path, 'wb')
            pickle.dump(sess, file, pickle.HIGHEST_PROTOCOL)
            file.close()
        except:
            msg = ">> Error saving session to file.\n"
            sys.stderr.write(msg)
            wx.MessageDialog(self.app.plot, msg, "Save session error", wx.ICON_WARNING | wx.OK).ShowModal()
            sys.stderr.write(traceback.format_exc())
        else:
            self.app.sess_status.saved = True
            s_rec = SessionRecord(sess)     # create a session record object for the current session
            # Find the index of the second rightmost "\" character in path
            i1 = path.rfind("\\")
            i2 = path.rfind("\\",0,i1)
            # Stored the path from the directory in which the patient file is stored
            s_rec.path = path[i2+1:]
            self.app.status.patient.add_session(s_rec)  # add record to the patient
            self.app.status.patient_changed = True
            
            try:
                # open a file object using the provided path and pickle the patient object
                file = open(ppath, 'wb')
                pickle.dump(cpat, file, pickle.HIGHEST_PROTOCOL)
                file.close()
            except:
                msg = "Error saving patient object to file.\n"
                sys.stderr.write(msg)
                wx.MessageDialog(None, msg, "Patient file error", wx.ICON_WARNING | wx.OK).ShowModal()
                sys.stderr.write(traceback.format_exc())
            else:
                self.app.status.patient_changed = False
                msg = ">> Patient file saved to: %s  -- Date: %s\n" % (cpat.path, wx.DateTime.Now().Format())
                self.app.status_msg(msg)
                sys.stdout.write(msg)
    
    def exit_plot(self, event):
        '''
        Closes the control panel and plot windows then returns to the main
        menu dialog.
        '''
        if self.app.sess_status.mode == Modes.StrTrl or self.app.sess_status.mode == Modes.SklTrl:
            if not self.app.sess_status.saved:
                dialog_text = "Save session data?"
                dial = wx.MessageDialog(None, dialog_text, 'Question',
                                        wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
                ret = dial.ShowModal()
                if ret == wx.ID_YES:
                    self.save_sess(None)
                    msg = ">>> Session ended manually at:" + wx.DateTime.Now().Format()
                    self.app.status_msg(msg)
                    print '>> Session saved'
                    print msg
        

        #self.app.cfg.y_axis_max = self.app.cfg.y_axis_max_init      # reset plot y axis max for the next session
        self.serial_disconnect(None)        # disconnect serial port
        self.app.console = None
        self.app.status.session = None      # reset reference to current session object
        self.app.sess_status = None         # reset reference to current session state object
        self.app.restore_mainmenu()
    
        
    def serial_connect(self, event):
        '''
        Connect to the serial device
        '''
        
        ser_ctrl = self.app.ctrl.serial_ctrl
        butt_ctrl = self.app.ctrl.but_ctrl
        plot_ctrl = self.app.plot.dCntl
        plot_conn = self.app.plot.dConn
        # Handler for connect button in control panel
        # Start serial thread
        
        self.app.console = Console(self.app.plot.update_data)
        
        self.app.sess_status.serial_open = True
        ser_ctrl.button_disconnect.Enable()
        ser_ctrl.button_connect.Disable()
        butt_ctrl.next_button.Enable()
        butt_ctrl.pause_button.Enable()
        plot_ctrl.nextBtn.setEnabled(True)
        plot_ctrl.pauseBtn.setEnabled(True)
        plot_conn.connBtn.setEnabled(False)
        plot_conn.disConnBtn.setEnabled(True)
        
        # Making sure the plot doesn't start
        self.app.sess_status.alive = False
            
    def serial_disconnect(self, event):
        '''
        Disconnect from the serial device
        '''
        
        
        ser_ctrl = self.app.ctrl.serial_ctrl
        butt_ctrl = self.app.ctrl.but_ctrl
        plot_ctrl = self.app.plot.dCntl
        plot_conn = self.app.plot.dConn

        # Handler for disconnect button in control panel
        if self.app.sess_status.serial_open:
            if self.app.console == None:
                pass
            else:
                self.app.console.stop()

                self.app.sess_status.serial_open = False
                ser_ctrl.button_disconnect.Disable()
                ser_ctrl.button_connect.Enable()
                butt_ctrl.next_button.Disable()
                butt_ctrl.pause_button.Disable()
                plot_ctrl.nextBtn.setEnabled(False)
                plot_ctrl.pauseBtn.setEnabled(False)
                plot_conn.connBtn.setEnabled(True)
                plot_conn.disConnBtn.setEnabled(False)
                    
        return self.app.sess_status.serial_open

# End of file "handlers.py"
        

